OPC Studio User's Guide and Reference
OPC UA PubSub logical resolution
Client and Subscriber Development > Development Models > Imperative Programming Model > Imperative Programming Model for OPC UA PubSub > Subscribing to Information (OPC UA PubSub) > OPC UA PubSub logical resolution
In This Topic

The OPC UA PubSub logical resolution is a mechanism that allows you to write code that specifies the information you work in logical terms (symbolic names of PubSub objects), and not in physical terms (such as the various identifier numbers that appear "on the wire").

The logical resolution is automatically used by OPC Data Client if, in the various descriptors you pass into the UnsubscribeDataSet Method, you use a logical identifier (PubSub object name, usually), and do not specify a physical identifier at the same time.

For quick tests and experiments with OPC UA PubSub, and especially if you do not want to lose time coding when you need to figure out the proper settings, you can use the OpcCmd Utility - see Using OpcCmd Utility as OPC UA PubSub Subscriber. The utility allows to specify all parameters of the dataset subscriptions that are accessible from code, but simply by entering them on the command line. It also has a built-in support for displaying the incoming datasets and their fields.

Resolver kinds

There are kinds of resolvers, depending on where the data needed for the logical resolution come from.

When the ResolverKind Property in the UAPubSubResolverDescriptor is set to UAPubSubResolverKind.None, the logical resolution cannot be performed. 

Publisher Endpoint Resolver

This resolver is used when the ResolverKind Property in the UAPubSubResolverDescriptor is set to UAPubSubResolverKind.PublisherEndpoint. The resolver connects to an OPC UA server which has the PubSub configuration model, and contains a configuration of the publisher whose messages you want to subscribe to. Based on the publisher configuration and the parameters and logical identifiers and you have specified, the resolver derives the parameters and physical identifiers needed for making the required subscription to the publisher's data. 

The OPC UA server that provides the publisher configuration may be integrated with the publisher in the same hardware or software component, but it can also be separate.

When selecting this resolver kind, you need to specify the URL (and possibly other characteristics) of the OPC UA server with the publisher configuration using the PublisherEndpointDescriptor Property in the UAPubSubResolverDescriptor. Instead of creating the PubSub resolver descriptor yourself by setting its kind and the URL, you can call the static Publisher Method and create it in one step.

Publisher File Resolver

This resolver is used when the ResolverKind Property in the UAPubSubResolverDescriptor is set to UAPubSubResolverKind.PublisherFile. The resolver opens a file with PubSub configuration model that contains configuration of the publisher whose messages you want to subscribe to. Based on the publisher configuration and the parameters logical identifiers and you have specified, the resolver derives the parameters and physical identifiers needed for making the required subscription to the publisher's data.

When selecting this resolver kind, you need to specify the PubSub configuration file path and name using the PublisherFileResourceDescriptor Property in the UAPubSubResolverDescriptor. Instead of creating the PubSub resolver descriptor yourself by setting its kind and file name, you can call the static File Method and create it one step.

The configuration file must be in the UABinary format as described in the OPC UA specification (UABinaryFileDataType in Annex A "Common Types" of Part 14).

The use of the publisher file resolver is illustrated in the example below.

.NET

// This example shows how to subscribe to dataset messages and specify a filter, resolving logical parameters to physical
// from an OPC-UA PubSub configuration file in binary format. The metadata obtained through the resolution is used to decode
// fixed layout messages with RawData field encoding.
//
// In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
// https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using System.Collections.Generic;
using System.Threading;
using OpcLabs.EasyOpc.UA.PubSub;
using OpcLabs.EasyOpc.UA.PubSub.OperationModel;

namespace UADocExamples.PubSub._EasyUASubscriber
{
    partial class SubscribeDataSet
    {
        public static void ResolveFromFile()
        {
            // Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
            // we have. The file itself is at the root of the project, and we have specified that it has to be copied to the
            // project's output directory.
            var pubSubResolverDescriptor = UAPubSubResolverDescriptor.File("UADemoPublisher-Default.uabinary");

            // Define the PubSub connection we will work with, using its logical name in the PubSub configuration.
            var pubSubConnectionDescriptor = new UAPubSubConnectionDescriptor { Name = "FixedLayoutConnection" };
            // In some cases you may have to set the interface (network adapter) name that needs to be used, similarly to
            // the statement below. Your actual interface name may differ, of course.
            //pubSubConnectionDescriptor.ResourceAddress.InterfaceName = "Ethernet";

            // Define the filter. The writer group and the dataset writer are specified using their logical names in the
            // PubSub configuration. The publisher Id in the filter will be taken from the logical PubSub connection.
            var filter = new UASubscribeDataSetFilter("FixedLayoutGroup", "SimpleWriter");

            // Instantiate the subscriber object and hook events.
            var subscriber = new EasyUASubscriber();
            subscriber.DataSetMessage += subscriber_DataSetMessage_ResolveFromFile;
            subscriber.ResolverAccess += subscriber_ResolverAccess_ResolveFromFile;

            Console.WriteLine("Subscribing...");
            subscriber.SubscribeDataSet(pubSubResolverDescriptor, pubSubConnectionDescriptor, filter);

            Console.WriteLine("Processing dataset message events for 20 seconds...");
            Thread.Sleep(20 * 1000);

            Console.WriteLine("Unsubscribing...");
            subscriber.UnsubscribeAllDataSets();

            Console.WriteLine("Waiting for 1 second...");
            // Unsubscribe operation is asynchronous, messages may still come for a short while.
            Thread.Sleep(1 * 1000);

            Console.WriteLine("Finished.");
        }

        static void subscriber_DataSetMessage_ResolveFromFile(object sender, EasyUADataSetMessageEventArgs e)
        {
            // Display the dataset.
            if (e.Succeeded)
            {
                // An event with null DataSetData just indicates a successful connection.
                if (!(e.DataSetData is null))
                {
                    Console.WriteLine();
                    Console.WriteLine($"Dataset data: {e.DataSetData}");
                    foreach (KeyValuePair<string, UADataSetFieldData> pair in e.DataSetData.FieldDataDictionary)
                        Console.WriteLine(pair);
                }
            }
            else
            {
                Console.WriteLine();
                Console.WriteLine($"*** Failure: {e.ErrorMessageBrief}");
            }
        }

        private static void subscriber_ResolverAccess_ResolveFromFile(object sender, EasyUAResolverAccessEventArgs e)
        {
            // Display resolution information.
            Console.WriteLine(e);
        }

        // Example output:
        //
        //Subscribing...
        //Processing dataset message events for 20 seconds...
        //[PublisherFile: UADemoPublisher-Default.uabinary] (no exception)
        //
        //Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
        //[BoolToggle, False {System.Boolean}; Good]
        //[Int32, 3072 {System.Int32}; Good]
        //[Int32Fast, 894 {System.Int32}; Good]
        //[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
        //[BoolToggle, False {System.Boolean}; Good]
        //[Int32, 3072 {System.Int32}; Good]
        //[Int32Fast, 920 {System.Int32}; Good]
        //[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
        //[BoolToggle, False {System.Boolean}; Good]
        //[Int32, 3073 {System.Int32}; Good]
        //[Int32Fast, 1003 {System.Int32}; Good]
        //[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
        //[BoolToggle, False {System.Boolean}; Good]
        //[Int32, 3073 {System.Int32}; Good]
        //[Int32Fast, 1074 {System.Int32}; Good]
        //[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
        //[BoolToggle, True {System.Boolean}; Good]
        //[Int32, 3074 {System.Int32}; Good]
        //[Int32Fast, 1140 {System.Int32}; Good]
        //[DateTime, 10/1/2019 12:21:16 PM {System.DateTime}; Good]
        //
        //...
    }
}
# This example shows how to subscribe to dataset messages and specify a filter, resolving logical parameters to physical
# from an OPC-UA PubSub configuration file in binary format. The metadata obtained through the resolution is used to
# decode fixed layout messages with RawData field encoding.
#
# In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
# https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
#
# Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
# OPC client and subscriber examples in Python on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-Python .
# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.BaseLib import *
from OpcLabs.EasyOpc.UA.PubSub import *
from OpcLabs.EasyOpc.UA.PubSub.OperationModel import *


def dataSetMessage(sender, e):
    # Display the dataset.
    if e.Succeeded:
        # An event with null DataSetData just indicates a successful connection.
        if e.DataSetData is not None:
            print('')
            print('Dataset data: ', e.DataSetData, sep='')
            for pair in e.DataSetData.FieldDataDictionary:
                print(pair)
    else:
        print('')
        print('*** Failure: ', e.ErrorMessageBrief, sep='')


def resolverAccess(sender, e):
    # Display resolution information.
    print(e)


# Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
# we have. The file itself is in this script's directory.
pubSubResolverDescriptor = UAPubSubResolverDescriptor.File(ResourceDescriptor('UADemoPublisher-Default.uabinary'))

# Define the PubSub connection we will work with, using its logical name in the PubSub configuration.
pubSubConnectionDescriptor = UAPubSubConnectionDescriptor()
pubSubConnectionDescriptor.Name = 'FixedLayoutConnection'
# In some cases you may have to set the interface (network adapter) name that needs to be used, similarly to
# the statement below. Your actual interface name may differ, of course.
#pubSubConnectionDescriptor.ResourceAddress.InterfaceName = 'Ethernet'

# Define the filter. The writer group and the dataset writer are specified using their logical names in the
# PubSub configuration. The publisher Id in the filter will be taken from the logical PubSub connection.
filter = UASubscribeDataSetFilter(
    UAWriterGroupDescriptor('FixedLayoutGroup'),
    UADataSetWriterDescriptor('SimpleWriter'))

# Instantiate the subscriber object and hook events.
subscriber = EasyUASubscriber()
subscriber.DataSetMessage += dataSetMessage
subscriber.ResolverAccess += resolverAccess

print('Subscribing...')
IEasyUASubscriberExtension.SubscribeDataSet(subscriber,
                                            pubSubResolverDescriptor,
                                            pubSubConnectionDescriptor,
                                            filter)

print('Processing dataset message events for 20 seconds...')
time.sleep(20)

print('Unsubscribing...')
subscriber.UnsubscribeAllDataSets()

print('Waiting for 1 second...')
# Unsubscribe operation is asynchronous, messages may still come for a short while.
time.sleep(1)

subscriber.DataSetMessage -= dataSetMessage
subscriber.ResolverAccess -= resolverAccess

print('Finished.')
' This example shows how to subscribe to dataset messages and specify a filter, resolving logical parameters to physical
' from an OPC-UA PubSub configuration file in binary format. The metadata obtained through the resolution is used to decode
' fixed layout messages with RawData field encoding.
'
' In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
' https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports OpcLabs.EasyOpc.UA.PubSub
Imports OpcLabs.EasyOpc.UA.PubSub.OperationModel

Namespace PubSub._EasyUASubscriber
    Partial Friend Class SubscribeDataSet
        Public Shared Sub ResolveFromFile()

            ' Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
            ' we have. The file itself is at the root of the project, and we have specified that it has to be copied to the
            ' project's output directory.
            Dim pubSubResolverDescriptor = UAPubSubResolverDescriptor.File("UADemoPublisher-Default.uabinary")

            ' Define the PubSub connection we will work with, using its logical name in the PubSub configuration.
            Dim pubSubConnectionDescriptor = New UAPubSubConnectionDescriptor
            pubSubConnectionDescriptor.Name = "FixedLayoutConnection"
            ' In some cases you may have to set the interface (network adapter) name that needs to be used, similarly to
            ' the statement below. Your actual interface name may differ, of course.
            ' pubSubConnectionDescriptor.ResourceAddress.InterfaceName = "Ethernet"

            ' Define the filter. The writer group and the dataset writer are specified using their logical names in the
            ' PubSub configuration. The publisher Id in the filter will be taken from the logical PubSub connection.
            Dim filter = New UASubscribeDataSetFilter("FixedLayoutGroup", "SimpleWriter")

            ' Instantiate the subscriber object and hook events.
            Dim subscriber = New EasyUASubscriber()
            AddHandler subscriber.DataSetMessage, AddressOf subscriber_DataSetMessage_ResolveFromFile
            AddHandler subscriber.ResolverAccess, AddressOf subscriber_ResolverAccess_ResolveFromFile

            Console.WriteLine("Subscribing...")
            subscriber.SubscribeDataSet(pubSubResolverDescriptor, pubSubConnectionDescriptor, filter)

            Console.WriteLine("Processing dataset message events for 20 seconds...")
            Threading.Thread.Sleep(20 * 1000)

            Console.WriteLine("Unsubscribing...")
            subscriber.UnsubscribeAllDataSets()

            Console.WriteLine("Waiting for 1 second...")
            ' Unsubscribe operation is asynchronous, messages may still come for a short while.
            Threading.Thread.Sleep(1 * 1000)

            Console.WriteLine("Finished...")
        End Sub

        Private Shared Sub subscriber_DataSetMessage_ResolveFromFile(ByVal sender As Object, ByVal e As EasyUADataSetMessageEventArgs)
            ' Display the dataset.
            If e.Succeeded Then
                ' An event with null DataSetData just indicates a successful connection.
                If e.DataSetData IsNot Nothing Then
                    Console.WriteLine()
                    Console.WriteLine($"Dataset data: {e.DataSetData}")
                    For Each pair As KeyValuePair(Of String, UADataSetFieldData) In e.DataSetData.FieldDataDictionary
                        Console.WriteLine(pair)
                    Next
                End If
            Else
                Console.WriteLine()
                Console.WriteLine($"*** Failure: {e.ErrorMessageBrief}")
            End If
        End Sub

        Private Shared Sub subscriber_ResolverAccess_ResolveFromFile(ByVal sender As Object, ByVal e As EasyUAResolverAccessEventArgs)
            ' Display resolution information.
            Console.WriteLine(e)
        End Sub
    End Class



    ' Example output
    '
    'Subscribing...
    'Processing dataset message events for 20 seconds...
    '
    '[PublisherFile: UADemoPublisher-Default.uabinary] (no exception)
    '
    'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
    '[BoolToggle, False {System.Boolean}; Good]
    '[Int32, 3072 {System.Int32}; Good]
    '[Int32Fast, 894 {System.Int32}; Good]
    '[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
    '[BoolToggle, False {System.Boolean}; Good]
    '[Int32, 3072 {System.Int32}; Good]
    '[Int32Fast, 920 {System.Int32}; Good]
    '[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
    '[BoolToggle, False {System.Boolean}; Good]
    '[Int32, 3073 {System.Int32}; Good]
    '[Int32Fast, 1003 {System.Int32}; Good]
    '[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
    '[BoolToggle, False {System.Boolean}; Good]
    '[Int32, 3073 {System.Int32}; Good]
    '[Int32Fast, 1074 {System.Int32}; Good]
    '[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
    '[BoolToggle, True {System.Boolean}; Good]
    '[Int32, 3074 {System.Int32}; Good]
    '[Int32Fast, 1140 {System.Int32}; Good]
    '[DateTime, 10/1/2019 12:21:16 PM {System.DateTime}; Good]
    '...

End Namespace

COM

// This example shows how to subscribe to dataset messages and specify a filter, resolving logical parameters to physical
// from an OPC-UA PubSub configuration file in binary format. The metadata obtained through the resolution is used to decode
// fixed layout messages with RawData field encoding.
//
// In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
// https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
//
// Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

type
  TSubscriberEventHandlers81 = class
    procedure OnDataSetMessage(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUADataSetMessageEventArgs);
    procedure OnResolverAccess(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUAResolverAccessEventArgs);
  end;

class procedure SubscribeDataSet.ResolveFromFile;
var
  SubscribeDataSetArguments: _EasyUASubscribeDataSetArguments;
  Subscriber: TEasyUASubscriber;
  SubscriberEventHandlers: TSubscriberEventHandlers81;
begin
  SubscribeDataSetArguments := CoEasyUASubscribeDataSetArguments.Create;

  // Define the PubSub connection we will work with, using its logical name in the PubSub configuration.
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ConnectionDescriptor.Name := 'FixedLayoutConnection';
  // In some cases you may have to set the interface (network adapter) name that needs to be used, similarly to
  // the statement below. Your actual interface name may differ, of course.
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ConnectionDescriptor.ResourceAddress.InterfaceName := 'Ethernet';

  // Define the filter. The writer group and the dataset writer are specified using their logical names in the
  // PubSub configuration. The publisher Id in the filter will be taken from the logical PubSub connection.
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.Filter.WriterGroupDescriptor.Name := 'FixedLayoutGroup';
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.Filter.DataSetWriterDescriptor.Name := 'SimpleWriter';

  // Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
  // we have. The file itself is included alongside the script.
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.PublisherFileResourceDescriptor.UrlString := 'UADemoPublisher-Default.uabinary';
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.ResolverKind := UAPubSubResolverKind_PublisherFile;

  // Instantiate the subscriber object and hook events.
  Subscriber := TEasyUASubscriber.Create(nil);
  SubscriberEventHandlers := TSubscriberEventHandlers81.Create;
  Subscriber.OnDataSetMessage := SubscriberEventHandlers.OnDataSetMessage;
  Subscriber.OnResolverAccess := SubscriberEventHandlers.OnResolverAccess;

  WriteLn('Subscribing...');
  Subscriber.SubscribeDataSet(SubscribeDataSetArguments);

  WriteLn('Processing dataset message for 20 seconds...');
  PumpSleep(20*1000);

  WriteLn('Unsubscribing...');
  Subscriber.UnsubscribeAllDataSets;

  WriteLn('Waiting for 1 second...');
  // Unsubscribe operation is asynchronous, messages may still come for a short while.
  PumpSleep(1*1000);

  WriteLn('Finished.');
  FreeAndNil(Subscriber);
  FreeAndNil(SubscriberEventHandlers);
end;

procedure TSubscriberEventHandlers81.OnDataSetMessage(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUADataSetMessageEventArgs);
var
  Count: Cardinal;
  DictionaryEntry2: _DictionaryEntry2;
  Element: OleVariant;
  FieldDataDictionaryEnumerator: IEnumVariant;
begin
  // Display the dataset.
  if eventArgs.Succeeded then
  begin
    // An event with null DataSetData just indicates a successful connection.
    if eventArgs.DataSetData <> nil then
    begin
      WriteLn;
      WriteLn('Dataset data: ', eventArgs.DataSetData.ToString);
      FieldDataDictionaryEnumerator := eventArgs.DataSetData.FieldDataDictionary.GetEnumerator;
      while (FieldDataDictionaryEnumerator.Next(1, Element, Count) = S_OK) do
      begin
        DictionaryEntry2 := IUnknown(Element) as _DictionaryEntry2;
        WriteLn(DictionaryEntry2.ToString);
      end;
    end;
  end
  else begin
    WriteLn;
      WriteLn('*** Failure: ', eventArgs.ErrorMessageBrief);
  end;
end;

procedure TSubscriberEventHandlers81.OnResolverAccess(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUAResolverAccessEventArgs);
begin
  // Display resolution information.
  WriteLn(eventArgs.ToString);
end;

// Example output:
//
//Subscribing...
//Processing dataset message events for 20 seconds...
//[PublisherFile: UADemoPublisher-Default.uabinary] (no exception)
//
//Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
//[BoolToggle, False {System.Boolean}; Good]
//[Int32, 3072 {System.Int32}; Good]
//[Int32Fast, 894 {System.Int32}; Good]
//[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
//
//Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
//[BoolToggle, False {System.Boolean}; Good]
//[Int32, 3072 {System.Int32}; Good]
//[Int32Fast, 920 {System.Int32}; Good]
//[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
//
//Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
//[BoolToggle, False {System.Boolean}; Good]
//[Int32, 3073 {System.Int32}; Good]
//[Int32Fast, 1003 {System.Int32}; Good]
//[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
//
//Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
//[BoolToggle, False {System.Boolean}; Good]
//[Int32, 3073 {System.Int32}; Good]
//[Int32Fast, 1074 {System.Int32}; Good]
//[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
//
//Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
//[BoolToggle, True {System.Boolean}; Good]
//[Int32, 3074 {System.Int32}; Good]
//[Int32Fast, 1140 {System.Int32}; Good]
//[DateTime, 10/1/2019 12:21:16 PM {System.DateTime}; Good]
//
//...
Rem This example shows how to subscribe to dataset messages and specify a filter, resolving logical parameters to physical
Rem from an OPC-UA PubSub configuration file in binary format. The metadata obtained through the resolution is used to decode
Rem fixed layout messages with RawData field encoding.
Rem
Rem In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
Rem https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
Rem
Rem Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Option Explicit

Const UAPubSubResolverKind_PublisherFile = 3

Dim SubscribeDataSetArguments: Set SubscribeDataSetArguments = CreateObject("OpcLabs.EasyOpc.UA.PubSub.OperationModel.EasyUASubscribeDataSetArguments")

' Define the PubSub connection we will work with, using its logical name in the PubSub configuration.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ConnectionDescriptor.Name = "FixedLayoutConnection"
' In some cases you may have to set the interface (network adapter) name that needs to be used, similarly to
' the statement below. Your actual interface name may differ, of course.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ConnectionDescriptor.ResourceAddress.InterfaceName = "Ethernet"

' Define the filter. The writer group and the dataset writer are specified using their logical names in the
' PubSub configuration. The publisher Id in the filter will be taken from the logical PubSub connection.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.Filter.WriterGroupDescriptor.Name = "FixedLayoutGroup"
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.Filter.DataSetWriterDescriptor.Name = "SimpleWriter"

' Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
' we have. The file itself is included alongside the script.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.PublisherFileResourceDescriptor.UrlString = "UADemoPublisher-Default.uabinary"
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.ResolverKind = UAPubSubResolverKind_PublisherFile

' Instantiate the subscriber object and hook events.
Dim Subscriber: Set Subscriber = CreateObject("OpcLabs.EasyOpc.UA.PubSub.EasyUASubscriber")
WScript.ConnectObject Subscriber, "Subscriber_"

WScript.Echo "Subscribing..."
Subscriber.SubscribeDataSet SubscribeDataSetArguments

WScript.Echo "Processing dataset message events for 20 seconds..."
WScript.Sleep 20*1000

WScript.Echo "Unsubscribing..."
Subscriber.UnsubscribeAllDataSets

WScript.Echo "Waiting for 1 second..."
' Unsubscribe operation is asynchronous, messages may still come for a short while.
WScript.Sleep 1*1000

WScript.Echo "Finished."



Sub Subscriber_DataSetMessage(Sender, e)
    ' Display the dataset.
    If e.Succeeded Then
        ' An event with null DataSetData just indicates a successful connection.
        If Not (e.DataSetData Is Nothing) Then
            WScript.Echo
            WScript.Echo "Dataset data: " & e.DataSetData
            Dim Pair: For Each Pair in e.DataSetData.FieldDataDictionary
                WScript.Echo Pair
            Next
        End If
    Else
        WScript.Echo
        WScript.Echo "*** Failure: " & e.ErrorMessageBrief
    End If
End Sub

Sub Subscriber_ResolverAccess(Sender, e)
    ' Display resolution information.
    WScript.Echo e
End Sub



' Example output:
'
'Subscribing...
'Processing dataset message events for 20 seconds...
'[PublisherFile: UADemoPublisher-Default.uabinary] (no exception)
'
'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
'[BoolToggle, False {System.Boolean}; Good]
'[Int32, 3072 {System.Int32}; Good]
'[Int32Fast, 894 {System.Int32}; Good]
'[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
'
'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
'[BoolToggle, False {System.Boolean}; Good]
'[Int32, 3072 {System.Int32}; Good]
'[Int32Fast, 920 {System.Int32}; Good]
'[DateTime, 10/1/2019 12:21:14 PM {System.DateTime}; Good]
'
'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
'[BoolToggle, False {System.Boolean}; Good]
'[Int32, 3073 {System.Int32}; Good]
'[Int32Fast, 1003 {System.Int32}; Good]
'[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
'
'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
'[BoolToggle, False {System.Boolean}; Good]
'[Int32, 3073 {System.Int32}; Good]
'[Int32Fast, 1074 {System.Int32}; Good]
'[DateTime, 10/1/2019 12:21:15 PM {System.DateTime}; Good]
'
'Dataset data: Good; Data; publisher=(UInt16)30, group=101, fields: 4
'[BoolToggle, True {System.Boolean}; Good]
'[Int32, 3074 {System.Int32}; Good]
'[Int32Fast, 1140 {System.Int32}; Good]
'[DateTime, 10/1/2019 12:21:16 PM {System.DateTime}; Good]
'
'...

 

Resolution rules

When specifying the identifiers for logical resolution, the conditions below must be fulfilled.

In general, there are two approaches that you can take when using the logical resolution (although you can also combine them, and also use physical identifiers in some places):

In the first case, the resolution provider finds the specified objects in the configuration, and uses their parameters to determine the proper subscription settings (such the PubSub connection descriptor, communication parameters, and subscribe dataset filter).

In the second case, the resolution provider finds the published dataset in the structure of the dataset folders in the PubSub configuration. It then attempts to find a dataset writer that is configured with this published dataset. Since the same dataset may be published by multiple dataset writers, the algorithm only considers as eligible the dataset writers that reside on PubSub connections with transport profile URI that the component supports.

The example below shows the approach with specifying published dataset name.

.NET

// This example shows how to subscribe to dataset messages, specifying just the published dataset name, and resolving all
// the dataset subscription arguments from an OPC-UA PubSub configuration file.
//
// In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
// https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using System.Collections.Generic;
using System.Threading;
using OpcLabs.EasyOpc.UA.PubSub;

namespace UADocExamples.PubSub._EasyUASubscriber
{
    partial class SubscribeDataSet
    {
        public static void PublishedDataSet()
        {
            // Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
            // we have. The file itself is at the root of the project, and we have specified that it has to be copied to the
            // project's output directory.
            var pubSubResolverDescriptor = UAPubSubResolverDescriptor.File("UADemoPublisher-Default.uabinary");

            // Instantiate the subscriber object.
            var subscriber = new EasyUASubscriber();

            Console.WriteLine("Subscribing...");
            // Specify the published dataset name, and let all other subscription arguments be resolved automatically.
            subscriber.SubscribeDataSet(pubSubResolverDescriptor, "AllTypes-Dynamic", (sender, args) =>
            {
                // Display the dataset.
                if (args.Succeeded)
                {
                    // An event with null DataSetData just indicates a successful connection.
                    if (!(args.DataSetData is null))
                    {
                        Console.WriteLine();
                        Console.WriteLine($"Dataset data: {args.DataSetData}");
                        foreach (KeyValuePair<string, UADataSetFieldData> pair in args.DataSetData.FieldDataDictionary)
                            Console.WriteLine(pair);
                    }
                }
                else
                {
                    Console.WriteLine();
                    Console.WriteLine($"*** Failure: {args.ErrorMessageBrief}");
                }
            });

            Console.WriteLine("Processing dataset message events for 20 seconds...");
            Thread.Sleep(20 * 1000);

            Console.WriteLine("Unsubscribing...");
            subscriber.UnsubscribeAllDataSets();

            Console.WriteLine("Waiting for 1 second...");
            // Unsubscribe operation is asynchronous, messages may still come for a short while.
            Thread.Sleep(1 * 1000);

            Console.WriteLine("Finished.");
        }

        // Example output:
        //
        //Subscribing...
        //Processing dataset message events for 20 seconds...
        //
        //Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
        //[BoolToggle, False {System.Boolean}; Good]
        //[Byte, 137 {System.Byte}; Good]
        //[Int16, 10377 {System.Int16}; Good]
        //[Int32, 43145 {System.Int32}; Good]
        //[Int64, 43145 {System.Int64}; Good]
        //[SByte, 9 {System.Int16}; Good]
        //[UInt16, 43145 {System.Int32}; Good]
        //[UInt32, 43145 {System.Int64}; Good]
        //[UInt64, 43145 {System.Decimal}; Good]
        //[Float, 43145 {System.Single}; Good]
        //[Double, 43145 {System.Double}; Good]
        //[String, Lima {System.String}; Good]
        //[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
        //[Guid, 45a99b50-e265-41f2-adea-d0bcedc3ff4b {System.Guid}; Good]
        //[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
        //[UInt32Array, [10] {43145, 43146, 43147, 43148, 43149, ...} {System.Int64[]}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
        //[BoolToggle, True {System.Boolean}; Good]
        //[Byte, 138 {System.Byte}; Good]
        //[Int16, 10378 {System.Int16}; Good]
        //[Int32, 43146 {System.Int32}; Good]
        //[Int64, 43146 {System.Int64}; Good]
        //[SByte, 10 {System.Int16}; Good]
        //[UInt16, 43146 {System.Int32}; Good]
        //[UInt32, 43146 {System.Int64}; Good]
        //[UInt64, 43146 {System.Decimal}; Good]
        //[Float, 43146 {System.Single}; Good]
        //[Double, 43146 {System.Double}; Good]
        //[String, Mike {System.String}; Good]
        //[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
        //[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
        //[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
        //[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
        //
        //Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
        //[DateTime, 10/3/2019 7:15:35 AM {System.DateTime}; Good]
        //[BoolToggle, True {System.Boolean}; Good]
        //[Byte, 138 {System.Byte}; Good]
        //[Int16, 10378 {System.Int16}; Good]
        //[Int32, 43146 {System.Int32}; Good]
        //[Int64, 43146 {System.Int64}; Good]
        //[SByte, 10 {System.Int16}; Good]
        //[UInt16, 43146 {System.Int32}; Good]
        //[UInt32, 43146 {System.Int64}; Good]
        //[UInt64, 43146 {System.Decimal}; Good]
        //[Float, 43146 {System.Single}; Good]
        //[Double, 43146 {System.Double}; Good]
        //[String, Mike {System.String}; Good]
        //[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
        //[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
        //[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
        //
        //...
    }
}
# This example shows how to subscribe to dataset messages, specifying just the published dataset name, and resolving all
# the dataset subscription arguments from an OPC-UA PubSub configuration file.
#
# In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
# https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
#
# Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
# OPC client and subscriber examples in Python on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-Python .
# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc
import time

# Import .NET namespaces.
from OpcLabs.BaseLib import *
from OpcLabs.EasyOpc.UA.PubSub import *
from OpcLabs.EasyOpc.UA.PubSub.OperationModel import *


def dataSetMessage(sender, e):
    # Display the dataset.
    if e.Succeeded:
        # An event with null DataSetData just indicates a successful connection.
        if e.DataSetData is not None:
            print('')
            print('Dataset data: ', e.DataSetData, sep='')
            for pair in e.DataSetData.FieldDataDictionary:
                print(pair)
    else:
        print('')
        print('*** Failure: ', e.ErrorMessageBrief, sep='')


# Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
# we have. The file itself is in this script's directory.
pubSubResolverDescriptor = UAPubSubResolverDescriptor.File(ResourceDescriptor('UADemoPublisher-Default.uabinary'))

# Instantiate the subscriber object.
subscriber = EasyUASubscriber()

print('Subscribing...')
# Specify the published dataset name, and let all other subscription arguments be resolved automatically.
IEasyUASubscriberExtension.SubscribeDataSet(subscriber,
                                            pubSubResolverDescriptor,
                                            'AllTypes-Dynamic',
                                            EasyUADataSetMessageEventHandler(dataSetMessage))

print('Processing dataset message events for 20 seconds...')
time.sleep(20)

print('Unsubscribing...')
subscriber.UnsubscribeAllDataSets()

print('Waiting for 1 second...')
# Unsubscribe operation is asynchronous, messages may still come for a short while.
time.sleep(1)

print('Finished.')
' This example shows how to subscribe to dataset messages, specifying just the published dataset name, and resolving all
' the dataset subscription arguments from an OPC-UA PubSub configuration file.
'
' In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
' https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports OpcLabs.EasyOpc.UA.PubSub

Namespace PubSub._EasyUASubscriber
    Partial Friend Class SubscribeDataSet
        Public Shared Sub PublishedDataSet()

            ' Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
            ' we have. The file itself is at the root of the project, and we have specified that it has to be copied to the
            ' project's output directory.
            Dim pubSubResolverDescriptor = UAPubSubResolverDescriptor.File("UADemoPublisher-Default.uabinary")

            ' Instantiate the subscriber object.
            Dim subscriber = New EasyUASubscriber()

            Console.WriteLine("Subscribing...")
            ' Specify the published dataset name, and let all other subscription arguments be resolved automatically.
            subscriber.SubscribeDataSet(pubSubResolverDescriptor, "AllTypes-Dynamic",
                Sub(sender, eventArgs)
                    '  Display the dataset.
                    If eventArgs.Succeeded Then
                        ' An event with null DataSetData just indicates a successful connection.
                        If Not eventArgs.DataSetData Is Nothing Then
                            Console.WriteLine()
                            Console.WriteLine("Dataset data: {0}", eventArgs.DataSetData)
                            For Each pair As KeyValuePair(Of String, UADataSetFieldData) In eventArgs.DataSetData.FieldDataDictionary
                                Console.WriteLine(pair)
                            Next
                        End If
                    Else
                        Console.WriteLine()
                        Console.WriteLine("*** Failure: {0}", eventArgs.ErrorMessageBrief)
                    End If
                End Sub)

            Console.WriteLine("Processing dataset message events for 20 seconds...")
            Threading.Thread.Sleep(20 * 1000)

            Console.WriteLine("Unsubscribing...")
            subscriber.UnsubscribeAllDataSets()

            Console.WriteLine("Waiting for 1 second...")
            ' Unsubscribe operation is asynchronous, messages may still come for a short while.
            Threading.Thread.Sleep(1 * 1000)

            Console.WriteLine("Finished...")
        End Sub
    End Class

    ' Example output
    '
    'Subscribing...
    'Processing dataset message events for 20 seconds...
    '
    'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
    '[BoolToggle, False {System.Boolean}; Good]
    '[Byte, 137 {System.Byte}; Good]
    '[Int16, 10377 {System.Int16}; Good]
    '[Int32, 43145 {System.Int32}; Good]
    '[Int64, 43145 {System.Int64}; Good]
    '[SByte, 9 {System.Int16}; Good]
    '[UInt16, 43145 {System.Int32}; Good]
    '[UInt32, 43145 {System.Int64}; Good]
    '[UInt64, 43145 {System.Decimal}; Good]
    '[Float, 43145 {System.Single}; Good]
    '[Double, 43145 {System.Double}; Good]
    '[String, Lima {System.String}; Good]
    '[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
    '[Guid, 45a99b50-e265-41f2-adea-d0bcedc3ff4b {System.Guid}; Good]
    '[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
    '[UInt32Array, [10] {43145, 43146, 43147, 43148, 43149, ...} {System.Int64[]}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
    '[BoolToggle, True {System.Boolean}; Good]
    '[Byte, 138 {System.Byte}; Good]
    '[Int16, 10378 {System.Int16}; Good]
    '[Int32, 43146 {System.Int32}; Good]
    '[Int64, 43146 {System.Int64}; Good]
    '[SByte, 10 {System.Int16}; Good]
    '[UInt16, 43146 {System.Int32}; Good]
    '[UInt32, 43146 {System.Int64}; Good]
    '[UInt64, 43146 {System.Decimal}; Good]
    '[Float, 43146 {System.Single}; Good]
    '[Double, 43146 {System.Double}; Good]
    '[String, Mike {System.String}; Good]
    '[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
    '[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
    '[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
    '[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
    '
    'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
    '[DateTime, 10/3/2019 7:15:35 AM {System.DateTime}; Good]
    '[BoolToggle, True {System.Boolean}; Good]
    '[Byte, 138 {System.Byte}; Good]
    '[Int16, 10378 {System.Int16}; Good]
    '[Int32, 43146 {System.Int32}; Good]
    '[Int64, 43146 {System.Int64}; Good]
    '[SByte, 10 {System.Int16}; Good]
    '[UInt16, 43146 {System.Int32}; Good]
    '[UInt32, 43146 {System.Int64}; Good]
    '[UInt64, 43146 {System.Decimal}; Good]
    '[Float, 43146 {System.Single}; Good]
    '[Double, 43146 {System.Double}; Good]
    '[String, Mike {System.String}; Good]
    '[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
    '[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
    '[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
    '...

End Namespace

COM

// This example shows how to subscribe to dataset messages, specifying just the published dataset name, and resolving all
// the dataset subscription arguments from an OPC-UA PubSub configuration file.
//
// In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
// https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
//
// Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

type
  TSubscriberEventHandlers79 = class
    procedure OnDataSetMessage(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUADataSetMessageEventArgs);
  end;

class procedure SubscribeDataSet.PublishedDataSet;
var
  SubscribeDataSetArguments: _EasyUASubscribeDataSetArguments;
  Subscriber: TEasyUASubscriber;
  SubscriberEventHandlers: TSubscriberEventHandlers79;
begin
  SubscribeDataSetArguments := CoEasyUASubscribeDataSetArguments.Create;

  // Specify the published dataset name, and let all other subscription arguments be resolved automatically.
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.PublishedDataSetName := 'AllTypes-Dynamic';

  // Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
  // we have. The file itself is included alongside the script.

  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.PublisherFileResourceDescriptor.UrlString := 'UADemoPublisher-Default.uabinary';
  SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.ResolverKind := UAPubSubResolverKind_PublisherFile;

  // Instantiate the subscriber object and hook events.
  Subscriber := TEasyUASubscriber.Create(nil);
  SubscriberEventHandlers := TSubscriberEventHandlers79.Create;
  Subscriber.OnDataSetMessage := SubscriberEventHandlers.OnDataSetMessage;

  WriteLn('Subscribing...');
  Subscriber.SubscribeDataSet(SubscribeDataSetArguments);

  WriteLn('Processing dataset message for 20 seconds...');
  PumpSleep(20*1000);

  WriteLn('Unsubscribing...');
  Subscriber.UnsubscribeAllDataSets;

  WriteLn('Waiting for 1 second...');
  // Unsubscribe operation is asynchronous, messages may still come for a short while.
  PumpSleep(1*1000);

  WriteLn('Finished.');
  FreeAndNil(Subscriber);
  FreeAndNil(SubscriberEventHandlers);
end;

procedure TSubscriberEventHandlers79.OnDataSetMessage(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUADataSetMessageEventArgs);
var
  Count: Cardinal;
  DictionaryEntry2: _DictionaryEntry2;
  Element: OleVariant;
  FieldDataDictionaryEnumerator: IEnumVariant;
begin
  // Display the dataset.
  if eventArgs.Succeeded then
  begin
    // An event with null DataSetData just indicates a successful connection.
    if eventArgs.DataSetData <> nil then
    begin
      WriteLn;
      WriteLn('Dataset data: ', eventArgs.DataSetData.ToString);
      FieldDataDictionaryEnumerator := eventArgs.DataSetData.FieldDataDictionary.GetEnumerator;
      while (FieldDataDictionaryEnumerator.Next(1, Element, Count) = S_OK) do
      begin
        DictionaryEntry2 := IUnknown(Element) as _DictionaryEntry2;
        WriteLn(DictionaryEntry2.ToString);
      end;
    end;
  end
  else begin
    WriteLn;
      WriteLn('*** Failure: ', eventArgs.ErrorMessageBrief);
  end;
end;

// Example output:
//
//Subscribing...
//Processing dataset message events for 20 seconds...
//
//Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
//[BoolToggle, False {System.Boolean}; Good]
//[Byte, 137 {System.Byte}; Good]
//[Int16, 10377 {System.Int16}; Good]
//[Int32, 43145 {System.Int32}; Good]
//[Int64, 43145 {System.Int64}; Good]
//[SByte, 9 {System.Int16}; Good]
//[UInt16, 43145 {System.Int32}; Good]
//[UInt32, 43145 {System.Int64}; Good]
//[UInt64, 43145 {System.Decimal}; Good]
//[Float, 43145 {System.Single}; Good]
//[Double, 43145 {System.Double}; Good]
//[String, Lima {System.String}; Good]
//[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
//[Guid, 45a99b50-e265-41f2-adea-d0bcedc3ff4b {System.Guid}; Good]
//[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
//[UInt32Array, [10] {43145, 43146, 43147, 43148, 43149, ...} {System.Int64[]}; Good]
//
//Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
//[BoolToggle, True {System.Boolean}; Good]
//[Byte, 138 {System.Byte}; Good]
//[Int16, 10378 {System.Int16}; Good]
//[Int32, 43146 {System.Int32}; Good]
//[Int64, 43146 {System.Int64}; Good]
//[SByte, 10 {System.Int16}; Good]
//[UInt16, 43146 {System.Int32}; Good]
//[UInt32, 43146 {System.Int64}; Good]
//[UInt64, 43146 {System.Decimal}; Good]
//[Float, 43146 {System.Single}; Good]
//[Double, 43146 {System.Double}; Good]
//[String, Mike {System.String}; Good]
//[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
//[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
//[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
//[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
//
//Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
//[DateTime, 10/3/2019 7:15:35 AM {System.DateTime}; Good]
//[BoolToggle, True {System.Boolean}; Good]
//[Byte, 138 {System.Byte}; Good]
//[Int16, 10378 {System.Int16}; Good]
//[Int32, 43146 {System.Int32}; Good]
//[Int64, 43146 {System.Int64}; Good]
//[SByte, 10 {System.Int16}; Good]
//[UInt16, 43146 {System.Int32}; Good]
//[UInt32, 43146 {System.Int64}; Good]
//[UInt64, 43146 {System.Decimal}; Good]
//[Float, 43146 {System.Single}; Good]
//[Double, 43146 {System.Double}; Good]
//[String, Mike {System.String}; Good]
//[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
//[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
//[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
//
//...
Rem This example shows how to subscribe to dataset messages, specifying just the published dataset name, and resolving all
Rem the dataset subscription arguments from an OPC-UA PubSub configuration file.
Rem
Rem In order to produce network messages for this example, run the UADemoPublisher tool. For documentation, see
Rem https://kb.opclabs.com/UADemoPublisher_Basics . In some cases, you may have to specify the interface name to be used.
Rem
Rem Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Option Explicit

Const UAPubSubResolverKind_PublisherFile = 3

Dim SubscribeDataSetArguments: Set SubscribeDataSetArguments = CreateObject("OpcLabs.EasyOpc.UA.PubSub.OperationModel.EasyUASubscribeDataSetArguments")

' Specify the published dataset name, and let all other subscription arguments be resolved automatically.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.PublishedDataSetName = "AllTypes-Dynamic"

' Define the PubSub resolver. We want the information be resolved from a PubSub binary configuration file that
' we have. The file itself is included alongside the script.
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.PublisherFileResourceDescriptor.UrlString = "UADemoPublisher-Default.uabinary"
SubscribeDataSetArguments.DataSetSubscriptionDescriptor.ResolverDescriptor.ResolverKind = UAPubSubResolverKind_PublisherFile

' Instantiate the subscriber object and hook events.
Dim Subscriber: Set Subscriber = CreateObject("OpcLabs.EasyOpc.UA.PubSub.EasyUASubscriber")
WScript.ConnectObject Subscriber, "Subscriber_"

WScript.Echo "Subscribing..."
Subscriber.SubscribeDataSet SubscribeDataSetArguments

WScript.Echo "Processing dataset message events for 20 seconds..."
WScript.Sleep 20*1000

WScript.Echo "Unsubscribing..."
Subscriber.UnsubscribeAllDataSets

WScript.Echo "Waiting for 1 second..."
' Unsubscribe operation is asynchronous, messages may still come for a short while.
WScript.Sleep 1*1000

WScript.Echo "Finished."



Sub Subscriber_DataSetMessage(Sender, e)
    ' Display the dataset.
    If e.Succeeded Then
        ' An event with null DataSetData just indicates a successful connection.
        If Not (e.DataSetData Is Nothing) Then
            WScript.Echo
            WScript.Echo "Dataset data: " & e.DataSetData
            Dim Pair: For Each Pair in e.DataSetData.FieldDataDictionary
                WScript.Echo Pair
            Next
        End If
    Else
        WScript.Echo
        WScript.Echo "*** Failure: " & e.ErrorMessageBrief
    End If
End Sub



' Example output:
'
'Subscribing...
'Processing dataset message events for 20 seconds...
'
'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
'[BoolToggle, False {System.Boolean}; Good]
'[Byte, 137 {System.Byte}; Good]
'[Int16, 10377 {System.Int16}; Good]
'[Int32, 43145 {System.Int32}; Good]
'[Int64, 43145 {System.Int64}; Good]
'[SByte, 9 {System.Int16}; Good]
'[UInt16, 43145 {System.Int32}; Good]
'[UInt32, 43145 {System.Int64}; Good]
'[UInt64, 43145 {System.Decimal}; Good]
'[Float, 43145 {System.Single}; Good]
'[Double, 43145 {System.Double}; Good]
'[String, Lima {System.String}; Good]
'[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
'[Guid, 45a99b50-e265-41f2-adea-d0bcedc3ff4b {System.Guid}; Good]
'[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
'[UInt32Array, [10] {43145, 43146, 43147, 43148, 43149, ...} {System.Int64[]}; Good]
'
'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
'[BoolToggle, True {System.Boolean}; Good]
'[Byte, 138 {System.Byte}; Good]
'[Int16, 10378 {System.Int16}; Good]
'[Int32, 43146 {System.Int32}; Good]
'[Int64, 43146 {System.Int64}; Good]
'[SByte, 10 {System.Int16}; Good]
'[UInt16, 43146 {System.Int32}; Good]
'[UInt32, 43146 {System.Int64}; Good]
'[UInt64, 43146 {System.Decimal}; Good]
'[Float, 43146 {System.Single}; Good]
'[Double, 43146 {System.Double}; Good]
'[String, Mike {System.String}; Good]
'[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
'[DateTime, 10/3/2019 7:15:34 AM {System.DateTime}; Good]
'[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
'[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
'
'Dataset data: Good; Data; publisher=(UInt64)31, writer=4, fields: 16
'[DateTime, 10/3/2019 7:15:35 AM {System.DateTime}; Good]
'[BoolToggle, True {System.Boolean}; Good]
'[Byte, 138 {System.Byte}; Good]
'[Int16, 10378 {System.Int16}; Good]
'[Int32, 43146 {System.Int32}; Good]
'[Int64, 43146 {System.Int64}; Good]
'[SByte, 10 {System.Int16}; Good]
'[UInt16, 43146 {System.Int32}; Good]
'[UInt32, 43146 {System.Int64}; Good]
'[UInt64, 43146 {System.Decimal}; Good]
'[Float, 43146 {System.Single}; Good]
'[Double, 43146 {System.Double}; Good]
'[String, Mike {System.String}; Good]
'[ByteString, [20] {176, 63, 39, 37, 31, ...} {System.Byte[]}; Good]
'[Guid, a0f06d75-9896-4fa3-9724-b564359da21b {System.Guid}; Good]
'[UInt32Array, [10] {43146, 43147, 43148, 43149, 43150, ...} {System.Int64[]}; Good]
'
'...

 

Message Receive Timeout Computation

When resolving from the publisher configuration, the resolution process is sometimes required to provide information that is somewhat difficult to obtain. The most prominent example of this is the MessageReceiveTimeout Property in the UADataSetSubscriptionDescriptor.CommunicationParameters. If you do not specify a non-zero value yourself, what should the resolution process fill in? The only hint it has from the provider configuration is the KeepAliveTime Property in the writer group (UAWriterGroupElement Class). But the keep alive time cannot directly be used as the message receive timeout: There needs to be some tolerance allowed, for computing and network delays. How big this tolerance should be is a big question, and depends on multiple parameters of your system.

In order to provide working results in most cases, the resolution provider uses a linear function to compute the message receive timeout from the keep alive time. The value of the keep alive time is multiple by message receive timeout factor (MessageReceiveTimeoutFactor Property), and then a message receive timeout increase (MessageReceiveTimeoutIncrease Property) is added to it.

The default message receive timeout factor is 1.05 (i.e. 5% over the keep alive time), and the default message receive timeout increase is 250 milliseconds. Example: For a keep-alive time of 1000 milliseconds, with the default parameters, the resulting message timeout will be 1300 milliseconds. If the computation described here does not work well for you, you can either set the message receive timeout directly to a value you have determined as proper, or you can modify the computation parameters so that they accommodate the keep alive times better to your system.

Data not in the message

When the resolver constructs the subscribe dataset filter (see Message Filtering (OPC UA PubSub)), it has knowledge about which pieces of data are included in the PubSub messages, and which are excluded. As explained with the subscribe dataset filter, filtering must not be specified on data pieces that are not included in the PubSub messages. The resolver sets the filter properties in such a way that no filtering is specified for information that is excluded from the PubSub messages, and guarantees that the filter will actually work correctly.

Resiliency

The publisher endpoint resolver and the publisher file resolver are periodic resolvers. This means that the resolution with them is not normally a one-time occurrence. This allows for long-running programs to achieve resiliency without further programming needed on your part.

If a periodic resolver encounters a failure obtaining the data it needs to perform the resolution, it will retry after a period given by the FailureRetrialDelay Property (in milliseconds). An inaccessibility of the data does not therefore result in permanent loss of the subscription. Note that, however, if there are no problems accessing the data needed to perform the resolution, the resolution will proceed, and if the data is flawed (e.g. in case of an incorrect configuration of the publisher), it may yield subscription parameters that won't work at all, or won't work properly. This kind of error cannot be easily detected.

If a periodic resolver succeeds in obtaining the data it needs to perform the resolution, it will still retry the resolution - but this time, after a period given by the SuccessRetrialDelay Property (in milliseconds). This behavior allows for improper configurations be fixed "on the fly": If there was a configuration error and the resolution yielded dysfunctional result, and the configuration error was later fixed, eventually, the resolver will pick up the new configuration, and make a new resolution, this time with a result that will be functional.

OPC Data Client does not necessarily create separate resolver objects for each dataset subscription you make. Instead, resolvers that use the same data source (e.g. access the same OPC UA server) are shared. For optimization reasons in this scenario, the delays that control the repeated resolution attempts described above are actually further modified by an additional MaximumWaitDelay Property (in milliseconds) in the parameters. This is kind of maximum allowed age of the resolution result. If a retrial has been scheduled for an earlier time, its resolution result will be used (sparing an unnecessary resolution attempt).

The resolver parameters mentioned above are all part of the PeriodicResolverParameters Class instance, stored in static EasyUASubscriber.AdaptableParameters.ResolverParameters.PublisherEndpointResolverParameters Property or PublisherFileResourceResolverParameters Property  (if the Isolated Property is set to 'false', which is the default), or in the corresponding sub-properties of the EasyUASubscriber.InstanceParameters.ResolverParameters Property Property (if you have set the Isolated Property to 'true'). 

The ResolverAccess Event

For advanced scenarios, your code is informed about certain aspects of the logical resolution process. This is done using the ResolverAccess Event on the EasyUASubscriber Class. With this event, you will receive event arguments that are an instance of EasyUAResolverAccessEventArgs Class. The event arguments contain the ResolverDescriptor Property which tell you the kind of the resolver involved, and its parameters.

The event is raised every time after the resolver accesses its information source. There are two possible outcomes:

The SubscriptionResolved Event

When the dataset subscription descriptor is successfully resolved, the component raises the SubscriptionResolved Event. This event contains the automatically resolved dataset subscription descriptor in its EasyUASubscriptionResolvedEventArgs.DataSetSubscriptionDescriptor Property. This allows your code to inspect it.

In addition, the event handlers for the SubscriptionResolved Event are allowed to modify the dataset subscription descriptor, and the modifications will be taken over by the component and use d for the actual subscription. This is useful in cases when the automated resolution is not complete, or fully correct.

For example, the PubSub configuration only contains endpoint URLs of Security Key Services (SKS), but no user authentication data with them. The automated resolution fill is in the URLs in the SecurityKeyServices Property of the UAPubSubCommunicationParameters Class, and you can write code inside the SubscriptionResolved Event handled to provide the use authentication information to authenticate the user to the SKS.

The resolved dataset subscription descriptor

In the DataSetMessage Event, after a successful logical resolution, the event arguments contain the final resolved dataset subscription descriptor in the ResolvedDataSetSubscriptionDescriptor Property. The resolved descriptor is derived from the one you passed to the SubscribeDataSet Method, with all the logical identifiers resolved to physical ones, and possible modifications made in the handlers for the SubscriptionResolved Event.

See Also

Examples - OPC UA PubSub

Reference